package com.peas.platform.framework.mybatisplus.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.peas.platform.common.core.configuration.RequestContext;
import com.peas.platform.common.core.configuration.RequestContextUtil;
import com.peas.platform.common.core.constant.Constants;
import com.peas.platform.common.core.dto.ResponseDto;
import com.peas.platform.common.core.feign.PeasModuleSysCoreFeign;
import com.peas.platform.common.core.utils.LogUtil;
import com.peas.platform.common.core.utils.RequestUtil;
import com.peas.platform.common.core.utils.SystemTool;
import com.peas.platform.framework.mybatisplus.interceptor.BaseInnerInterceptor;
import com.peas.platform.framework.mybatisplus.po.BasePo;
import com.peas.platform.framework.mybatisplus.repository.BaseRepository;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.Connection;
import java.util.*;
import java.util.regex.Matcher;

@Component
@Slf4j
public class AlphaInnerInterceptor implements InnerInterceptor {

    @Value("${spring.profiles.active:local}")
    private String profiles;

    @Resource
    private ApplicationContext ac;
    @Resource
    private PeasModuleSysCoreFeign peasModuleSysCoreFeign;
    @Resource
    private SystemTool systemTool;

    private String getSql(BoundSql boundSql) {
        return boundSql.getSql();
    }

    private Map<String, Object> getParam(Configuration configuration, BoundSql boundSql) {
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (CollectionUtil.isEmpty(parameterMappings)) {
            return new HashMap<>();
        }
        Map<String, Object> map = new HashMap<>();
        Object parameterObject = boundSql.getParameterObject();
        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() == ParameterMode.OUT) {
                continue;
            }
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            map.put(parameterMapping.getProperty(), value);
        }
        return map;
    }

    private String beautifySql(String sql) {
        String s = sql.replaceAll("\r\n", " ").replaceAll("\n", " ");
        while (s.contains("  ")) {
            s = s.replaceAll(" {2}", " ");
        }
        return s;
    }

    @Override
    public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        log.trace("#####willDoQuery");
        return true;
    }

    private List<BaseInnerInterceptor> getBaseInnerInterceptors() {
        List<BaseInnerInterceptor> list = Collections.emptyList();
        Map<String, BaseInnerInterceptor> beans = ac.getBeansOfType(BaseInnerInterceptor.class);
        if (CollUtil.isNotEmpty(beans)) {
            list = new ArrayList<>(beans.values());
            list.sort((o1, o2) -> o1.sort() == null ? 1 : o2.sort() == null ? -1 : o1.sort().compareTo(o2.sort()));
        }
        return list;
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        log.trace("#####beforeQuery");
        SqlCommandType sqlCommandType = ms.getSqlCommandType();
        log.debug("【sql语句类型】:" + sqlCommandType);
        String sql = beautifySql(getSql(boundSql));
        log.info("【sql语句内容】:" + sql);
        Map<String, Object> param = getParam(ms.getConfiguration(), boundSql);
        log.info("【sql语句参数】:" + JSONUtil.toJsonStr(param));
        String method = ms.getId();
        List<BaseInnerInterceptor> list = getBaseInnerInterceptors();
        switch (sqlCommandType) {
            case UNKNOWN:
                debug("beforeQuery", SqlCommandType.UNKNOWN, method, sql, param);
                break;
            case INSERT:
                debug("beforeQuery", SqlCommandType.INSERT, method, sql, param);
                break;
            case UPDATE:
                debug("beforeQuery", SqlCommandType.UPDATE, method, sql, param);
                break;
            case DELETE:
                debug("beforeQuery", SqlCommandType.DELETE, method, sql, param);
                break;
            case SELECT:
                debug("beforeQuery", SqlCommandType.SELECT, method, sql, param);
                CollUtil.forEach(list, (CollUtil.Consumer<BaseInnerInterceptor>) (value, index) -> value.beforeSelect(method, sql, param));
                break;
            case FLUSH:
                debug("beforeQuery", SqlCommandType.FLUSH, method, sql, param);
                break;
        }
    }

    @Override
    public boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) {
        log.trace("#####willDoUpdate");
        return true;
    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        log.trace("#####beforeUpdate");
        BoundSql boundSql = ms.getBoundSql(parameter);
        SqlCommandType sqlCommandType = ms.getSqlCommandType();
        log.debug("【sql语句类型】:" + sqlCommandType);
        final String sql = beautifySql(getSql(boundSql));
        log.info("【sql语句内容】:" + sql);
        final Map<String, Object> param = getParam(ms.getConfiguration(), boundSql);
        log.info("【sql语句参数】:" + JSONUtil.toJsonStr(param));
        final String method = ms.getId();
        List<BaseInnerInterceptor> list = getBaseInnerInterceptors();
        switch (sqlCommandType) {
            case UNKNOWN:
                debug("beforeUpdate", SqlCommandType.UNKNOWN, method, sql, param);
                break;
            case INSERT:
                debug("beforeUpdate", SqlCommandType.INSERT, method, sql, param);
                CollUtil.forEach(list, (CollUtil.Consumer<BaseInnerInterceptor>) (value, index) -> value.beforeInsert(method, sql, param));
                break;
            case UPDATE:
                debug("beforeUpdate", SqlCommandType.UPDATE, method, sql, param);
                CollUtil.forEach(list, (CollUtil.Consumer<BaseInnerInterceptor>) (value, index) -> value.beforeUpdate(method, sql, param));
                break;
            case DELETE:
                debug("beforeUpdate", SqlCommandType.DELETE, method, sql, param);
                if (!StrUtil.equals("local", profiles)) {
                    final RequestContext rc = RequestContextUtil.getRequestContext();
                    new Thread(() -> {
                        RequestUtil.initRequestId(rc);
                        AlphaInnerInterceptor.this.recycle(method, sql, param, 0L);
                    }).start();
                }
                CollUtil.forEach(list, (CollUtil.Consumer<BaseInnerInterceptor>) (value, index) -> value.beforeDelete(method, sql, param));
                break;
            case SELECT:
                debug("beforeUpdate", SqlCommandType.SELECT, method, sql, param);
                break;
            case FLUSH:
                debug("beforeUpdate", SqlCommandType.FLUSH, method, sql, param);
                break;
        }
    }

    private void debug(String before, SqlCommandType sqlCommandType, String method, String sql, Map<String, Object> param) {
        switch (sqlCommandType) {
            case UNKNOWN:
                log.error("sqlCommandType is UNKNOWN");
                break;
            case INSERT:
                log.debug("-------insert拦截器 of " + before);
                log.debug(method);
                log.debug(sql);
                log.debug(JSONUtil.toJsonStr(param));
                log.debug(getFllSql(sql, param));
                log.debug("-------insert拦截器");
                break;
            case UPDATE:
                log.debug("-------update拦截器 of " + before);
                log.debug(method);
                log.debug(sql);
                log.debug(JSONUtil.toJsonStr(param));
                log.debug(getFllSql(sql, param));
                log.debug("-------update拦截器");
                break;
            case DELETE:
                log.debug("-------delete拦截器 of " + before);
                log.debug(method);
                log.debug(sql);
                log.debug(JSONUtil.toJsonStr(param));
                log.debug(getFllSql(sql, param));
                log.debug("-------delete拦截器");
                break;
            case SELECT:
                log.debug("-------select拦截器 of " + before);
                log.debug(method);
                log.debug(sql);
                log.debug(JSONUtil.toJsonStr(param));
                log.debug(getFllSql(sql, param));
                log.debug("-------select拦截器");
                break;
            case FLUSH:
                log.error("sqlCommandType is FLUSH");
                break;
        }
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        log.trace("#####beforePrepare");
    }

    @Override
    public void beforeGetBoundSql(StatementHandler sh) {
        log.trace("#####beforeGetBoundSql");
    }

    @Override
    public void setProperties(Properties properties) {
        log.trace("#####setProperties");
    }

    private String getFllSql(String sql, Map<String, Object> param) {
        if (CollUtil.isNotEmpty(param)) {
            for (Object o : param.values()) {
                //sql = sql.replaceFirst("\\?", formatParamValue(o));
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(formatParamValue(o)));
            }
        }
        return sql;
    }

    private String formatParamValue(Object paramValue) {
        if (paramValue == null) {
            return "null";
        }
        if (paramValue instanceof String) {
            paramValue = "'" + paramValue + "'";
        }
        if (paramValue instanceof Date) {
            paramValue = "'" + paramValue + "'";
        }
        return paramValue.toString();
    }

    @SneakyThrows
    private void recycle(String method, String sql, Map<String, Object> param, long duration) {
        if (systemTool.special()) {
            LogUtil.log(this.getClass(), "recycle", "method:" + method + ",sql:" + sql + ",param:" + param, duration);
            return;
        }
        String c = method.substring(0, method.lastIndexOf('.'));
        String m = method.substring(method.lastIndexOf('.') + 1);
        String seq = Constants.snowflake.nextIdStr();
        BaseRepository<?> repo = (BaseRepository<?>) ac.getBean(Class.forName(c));
        log.debug("doDelete,method:" + method);
        log.debug("doDelete,sql:" + sql);
        log.debug("doDelete,param:" + param);
        log.debug("doDelete,c:" + c);
        log.debug("doDelete,m:" + m);
        switch (m) {
            case "deleteById":
                BasePo po = repo.selectById((Long) param.get("id"));
                if (po != null) {
                    String content = JSONUtil.toJsonStr(po);
                    JSONObject json = JSON.parseObject(content);
                    String table = sql.split(" ")[2];
                    table = table.substring(1, table.length() - 1);
                    cn.hutool.json.JSONObject data = systemTool.buildJSONObject();
                    data.set("tableName", table);
                    data.set("tableId", json.getLong("id"));
                    data.set("content", content);
                    data.set("deleteSeq", seq);
                    data.set("deleteTime", new Date());
                    data.set("deleteBy", RequestContextUtil.getCurrentUserId());
                    data.set("deleteRemark", "");
                    log.debug("call peasModuleSysMdmFeign.addRecycle:" + JSONUtil.toJsonStr(data));
                    ResponseDto<Integer> result = peasModuleSysCoreFeign.addRecycle(data);
                    log.debug("call peasModuleSysMdmFeign.addRecycle, result:" + result);
                }
                break;
            case "delete":
                break;
        }
    }

}
